<?php

namespace app\api\service\common\token;

use app\common\Constants;
use app\common\ErrorCode;
use app\common\exception\BusinessException;
use app\common\facade\Principal;
use Firebase\JWT\BeforeValidException;
use Firebase\JWT\ExpiredException;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Firebase\JWT\SignatureInvalidException;
use think\exception\ValidateException;
use think\facade\Config;
use think\facade\Log;

class JwtTokenService implements TokenServiceInterface
{
    protected CacheTokenRepository $tokenRepository;

    public function __construct()
    {
        $this->tokenRepository = new CacheTokenRepository();
    }

    public function validateToken(string $token): array
    {
        // 解码Token
        $decodeResult = $this->decodeAccessToken($token);

        // 如果不允许多端登录，检查此Token的状态
        if (!Config::get("rds.multi_login")) {
            $tokenData = $this->tokenRepository->find($token, 'access');
            if ($tokenData) {
                throw new BusinessException(Constants::E_COMMON_MULTI_LOGIN_FORBIDDEN,ErrorCode::E_COMMON_MULTI_LOGIN_FORBIDDEN, );
            }
        }
        return $decodeResult;
    }

    /**
     * 解码Token
     * @param string $token
     * @return array
     */
    protected function decodeAccessToken(string $token): array
    {
        $secretKey = Config::get("jwt.secret");
        $algo = Config::get("jwt.algo");
        try {
            JWT::$leeway = 5;
            $decoded = JWT::decode($token, new Key($secretKey, $algo));
            return (array)$decoded;
        } catch (ExpiredException $e) {
            throw new BusinessException(Constants::E_COMMON_ACCESS_TOKEN_EXPIRED,ErrorCode::E_COMMON_ACCESS_TOKEN_EXPIRED, );
        } catch (SignatureInvalidException $e) {
            throw new BusinessException(Constants::E_COMMON_ACCESS_TOKEN_INVALID,ErrorCode::E_COMMON_ACCESS_TOKEN_INVALID, );
        } catch (BeforeValidException $e) {
            throw new BusinessException(Constants::E_COMMON_ACCESS_TOKEN_INVALID,ErrorCode::E_COMMON_ACCESS_TOKEN_INVALID, );
        } catch (\UnexpectedValueException $e) {
            throw new BusinessException(Constants::E_COMMON_ACCESS_TOKEN_INVALID,ErrorCode::E_COMMON_ACCESS_TOKEN_INVALID, );
        }
    }

    /**
     * 解码Token
     * @param string $token
     * @return array
     */
    protected function decodeRefreshToken(string $token): array
    {
        $secretKey = Config::get("jwt.secret");
        $algo = Config::get("jwt.algo");
        try {
            JWT::$leeway = 5;
            $decoded = JWT::decode($token, new Key($secretKey, $algo));
            return (array)$decoded;
        } catch (ExpiredException $e) {
            throw new BusinessException(Constants::E_COMMON_REFRESH_TOKEN_EXPIRED,ErrorCode::E_COMMON_REFRESH_TOKEN_EXPIRED, );
        } catch (SignatureInvalidException $e) {
            throw new BusinessException(Constants::E_COMMON_REFRESH_TOKEN_INVALID,ErrorCode::E_COMMON_REFRESH_TOKEN_INVALID, );
        } catch (BeforeValidException $e) {
            throw new BusinessException(Constants::E_COMMON_REFRESH_TOKEN_INVALID,ErrorCode::E_COMMON_REFRESH_TOKEN_INVALID, );
        } catch (\UnexpectedValueException $e) {
            throw new BusinessException(Constants::E_COMMON_REFRESH_TOKEN_INVALID,ErrorCode::E_COMMON_REFRESH_TOKEN_INVALID, );
        }
    }

    public function refreshToken(string $refreshToken): array
    {
        try {
            // 如果不允许多端登录，检查此Token的状态
            if (!Config::get("rds.multi_login")) {
                $tokenData = $this->tokenRepository->findByUser(Principal::getUserName());
                if (!$tokenData) {
                    throw new ValidateException(Constants::E_COMMON_MULTI_LOGIN_FORBIDDEN);
                }
            }

            // 检查Token是否有效
            $decodeResult = $this->decodeRefreshToken($refreshToken);

            // 删除旧的refresh token
            $this->tokenRepository->delete($refreshToken, 'refresh');

            // 生成新的token
            $newTokenInfo = $this->generateToken((array)$decodeResult['data']);

            return $newTokenInfo;
        } catch (\Exception $e) {
            // 删除失效的refresh token
            $this->tokenRepository->delete($refreshToken, 'refresh');
            Log::error("Failed to refresh token: " . $e->getMessage());
            throw $e;
        }
    }

    public function generateToken(array $data): array
    {
        try {
            // 编码 Token
            $tokenInfo = $this->encodeToken($data);

            trace(json_encode($tokenInfo),"Generated token: ");

            // 将Token存储到存储层
            $this->tokenRepository->save($tokenInfo, 'access');

            return $tokenInfo;
        } catch (\Exception $e) {
            Log::error("Failed to generate token: " . $e->getMessage());
            throw $e;
        }
    }

    /**
     * 编码Token
     * @param array $data
     * @return array
     */
    protected function encodeToken(array $data): array
    {
        $jwt_config = Config::get('jwt');
        $current_time = time();
        $payload = [
            "iss" => $jwt_config['issuer'],
            "aud" => $jwt_config['audience'],
            "iat" => microtime(true),
            'jti' => bin2hex(random_bytes(16)),
            'nbf' => $current_time + 5,
            "exp" => $current_time + $jwt_config['ttl'],
            "data" => $data
        ];
        $accessToken = JWT::encode($payload, $jwt_config['secret'], $jwt_config['algo']);

        $refreshTokenPayload = [
            "iss" => $jwt_config['issuer'],
            "aud" => $jwt_config['audience'],
            "iat" => microtime(true),
            'jti' => bin2hex(random_bytes(16)),
            'nbf' => $current_time + 5,
            "exp" => $current_time + $jwt_config['refresh_ttl'],
            "data" => $data
        ];
        $refreshToken = JWT::encode($refreshTokenPayload, $jwt_config['secret'], $jwt_config['algo']);

        return [
            "id" => $data['id'],
            "username" => $data['username'],
            "accessToken" => $accessToken,
            "refreshToken" => $refreshToken,
            "expireTime" => $current_time + $jwt_config['ttl'],
            "refreshExpireTime" => $current_time + $jwt_config['refresh_ttl']
        ];
    }

    public function disableUserTokens(string $username): void
    {
        try {
            $this->tokenRepository->deleteByUser($username);
            trace("Disabled all tokens for user: {$username}");
        } catch (\Exception $e) {
            Log::error("Failed to disable user tokens: " . $e->getMessage());
            throw $e;
        }
    }

    public function clearExpiredTokens(): void
    {
        try {
            $this->tokenRepository->deleteExpired();
            trace("Cleared expired tokens");
        } catch (\Exception $e) {
            Log::error("Failed to clear expired tokens: " . $e->getMessage());
            throw $e;
        }
    }

    public function deleteToken(string $token, string $type): void
    {
        try {
            $this->tokenRepository->delete($token, $type);
            trace("Deleted token: {$type}");
        } catch (\Exception $e) {
            Log::error("Failed to delete token: " . $e->getMessage());
            throw $e;
        }
    }

    public function updateOnline(string $token): void
    {
        try {
            $this->tokenRepository->updateOnline($token);
        } catch (\Exception $e) {
            Log::error("Failed to update token status : " . $e->getMessage());
            throw $e;
        }
    }

}


